/** * Copyright (C) 2012-2015 Dell, Inc * See annotations for authorship information * * ==================================================================== * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ==================================================================== */ package org.dasein.cloud.google; import java.io.*; import java.net.InetSocketAddress; import java.net.Proxy; import java.security.KeyStore; import java.security.PrivateKey; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.services.compute.Compute; import com.google.api.services.compute.ComputeScopes; import com.google.api.services.replicapool.Replicapool; import com.google.api.services.sqladmin.SQLAdmin; import com.google.api.services.sqladmin.SQLAdminScopes; import com.google.api.services.storage.Storage; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.dasein.cloud.AbstractCloud; import org.dasein.cloud.CloudErrorType; import org.dasein.cloud.CloudException; import org.dasein.cloud.ContextRequirements; import org.dasein.cloud.InternalException; import org.dasein.cloud.ProviderContext; import org.dasein.cloud.google.LogHandler; import org.dasein.cloud.google.compute.GoogleCompute; import org.dasein.cloud.google.network.GoogleNetwork; import org.dasein.cloud.google.platform.GooglePlatform; import org.dasein.cloud.google.storage.GoogleDrive; import org.dasein.cloud.util.Cache; import org.dasein.cloud.util.CacheLevel; import org.dasein.util.uom.time.Hour; import org.dasein.util.uom.time.TimePeriod; import org.dasein.cloud.ci.CIServices; import org.dasein.cloud.ci.GoogleCIServices; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * Support for the Google API through Dasein Cloud. * <p>Created by George Reese: 12/06/2012 9:35 AM</p> * @author George Reese * @version 2013.01 initial version * @since 2013.01 */ public class Google extends AbstractCloud { static private final Logger logger = getLogger(Google.class); private static final String DSN_P12_CERT = "p12Certificate"; private static final String DSN_SERVICE_ACCOUNT = "serviceAccount"; public final static String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; public final static String ISO8601_NO_MS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; public static final Set<String> sqlScope = new HashSet<String>(Arrays.asList(SQLAdminScopes.CLOUD_PLATFORM,SQLAdminScopes.SQLSERVICE_ADMIN)); private final static CustomHttpRequestInitializer initializer = new CustomHttpRequestInitializer(); private JsonFactory jsonFactory = null; private Cache<GoogleCredential> cachedCredentials = null; private Cache<Compute> computeCache = null; private Cache<Storage> storageCache = null; private Cache<Replicapool> replicapoolCache = null; private Cache<GoogleCredential> cachedSqlCredentials = null; private Cache<SQLAdmin> sqlCache = null; static private @Nonnull String getLastItem(@Nonnull String name) { int idx = name.lastIndexOf('.'); if( idx < 0 ) { return name; } else if( idx == (name.length()-1) ) { return ""; } return name.substring(idx + 1); } static public @Nonnull Logger getLogger(@Nonnull Class<?> cls) { String pkg = getLastItem(cls.getPackage().getName()); if( pkg.equals("google") ) { pkg = ""; } else { pkg = pkg + "."; } return Logger.getLogger("dasein.cloud.google.std." + pkg + getLastItem(cls.getName())); } static public @Nonnull Logger getWireLogger(@Nonnull Class<?> cls) { return Logger.getLogger("dasein.cloud.google.wire." + getLastItem(cls.getPackage().getName()) + "." + getLastItem(cls.getName())); } public Google() { jsonFactory = new JacksonFactory(); cachedCredentials = Cache.getInstance(this, "Credentials", GoogleCredential.class, CacheLevel.CLOUD_ACCOUNT, new TimePeriod<Hour>(1, TimePeriod.HOUR)); computeCache = Cache.getInstance(this, "ComputeAccess", Compute.class, CacheLevel.CLOUD_ACCOUNT, new TimePeriod<Hour>(1, TimePeriod.HOUR)); storageCache = Cache.getInstance(this, "DriveAccess", Storage.class, CacheLevel.CLOUD_ACCOUNT, new TimePeriod<Hour>(1, TimePeriod.HOUR)); replicapoolCache = Cache.getInstance(this, "ReplicapoolAccess", Replicapool.class, CacheLevel.CLOUD_ACCOUNT, new TimePeriod<Hour>(1, TimePeriod.HOUR)); cachedSqlCredentials = Cache.getInstance(this, "SqlCredentials", GoogleCredential.class, CacheLevel.CLOUD_ACCOUNT, new TimePeriod<Hour>(1, TimePeriod.HOUR)); sqlCache = Cache.getInstance(this, "SqlAccess", SQLAdmin.class, CacheLevel.CLOUD_ACCOUNT, new TimePeriod<Hour>(1, TimePeriod.HOUR)); } @Override public @Nonnull String getCloudName() { ProviderContext ctx = getContext(); String name = (ctx == null ? null : ctx.getCloudName()); return (name == null ? "GCE" : name); } @Override public @Nonnull ContextRequirements getContextRequirements() { return new ContextRequirements( new ContextRequirements.Field(DSN_P12_CERT, "The p12 file for the account", ContextRequirements.FieldType.KEYPAIR, ContextRequirements.Field.X509, true), new ContextRequirements.Field(DSN_SERVICE_ACCOUNT, "The service account email registered to the account", ContextRequirements.FieldType.TEXT, ContextRequirements.Field.ACCESS_KEYS, true), new ContextRequirements.Field("proxyHost", "Proxy host", ContextRequirements.FieldType.TEXT, null, false), new ContextRequirements.Field("proxyPort", "Proxy port", ContextRequirements.FieldType.TEXT, null, false) ); } @Override public @Nonnull DataCenters getDataCenterServices() { return new DataCenters(this); } @Override public @Nonnull GoogleCompute getComputeServices() { return new GoogleCompute(this); } @Override public @Nonnull GoogleNetwork getNetworkServices() { return new GoogleNetwork(this); } @Override public @Nullable GooglePlatform getPlatformServices() { return new GooglePlatform(this); } public @Nullable String getProxyHost() { ProviderContext ctx = getContext(); if( ctx == null ) { return null; } Properties props = ctx.getCustomProperties(); return ( props == null ? null : props.getProperty("proxyHost") ); } public int getProxyPort() { ProviderContext ctx = getContext(); if( ctx == null ) { return -1; } Properties props = ctx.getCustomProperties(); if( props == null ) { return -1; } String port = props.getProperty("proxyPort"); if( port != null ) { return Integer.parseInt(port); } return -1; } @Override public @Nonnull GoogleDrive getStorageServices(){ return new GoogleDrive(this); } @Override public @Nonnull String getProviderName() { ProviderContext ctx = getContext(); String name = (ctx == null ? null : ctx.getCloud().getCloudName()); return (name == null ? "Google" : name); } private HttpTransport getTransport() { HttpTransport transport = null; int proxyPort = -1; String proxyHost = null; List<ContextRequirements.Field> fields = getContextRequirements().getConfigurableValues(); for(ContextRequirements.Field f : fields ) if ((f.compatName == null) && (f.name.equals("proxyHost"))) proxyHost = getProxyHost(); else if ((f.compatName == null) && (f.name.equals("proxyPort"))) proxyPort = getProxyPort(); if ( proxyHost != null && proxyHost.length() > 0 && proxyPort > 0 ) { Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)); transport = new NetHttpTransport.Builder().setProxy(proxy).build(); } else transport = new NetHttpTransport(); return transport; } private GoogleCredential getCreds(HttpTransport transport, JsonFactory jsonFactory, Collection<String> scopes) throws Exception { byte[] p12Bytes = null; String p12Password = ""; String serviceAccountId = ""; List<ContextRequirements.Field> fields = getContextRequirements().getConfigurableValues(); try { for(ContextRequirements.Field f : fields ) { if(f.type.equals(ContextRequirements.FieldType.KEYPAIR)){ byte[][] keyPair = (byte[][])getContext().getConfigurationValue(f); p12Bytes = keyPair[0]; p12Password = new String(keyPair[1], "utf-8"); } else if(f.compatName != null && f.compatName.equals(ContextRequirements.Field.ACCESS_KEYS)) serviceAccountId = (String)getContext().getConfigurationValue(f); } } catch(Exception ex) { throw new CloudException(CloudErrorType.AUTHENTICATION, 400, "Bad Credentials", "An authentication error has occurred: Bad Credentials"); } KeyStore keyStore = KeyStore.getInstance("PKCS12"); InputStream p12AsStream = new ByteArrayInputStream(p12Bytes); keyStore.load(p12AsStream, p12Password.toCharArray()); GoogleCredential creds = new GoogleCredential.Builder().setTransport(transport) .setJsonFactory(jsonFactory) .setServiceAccountId(serviceAccountId) .setServiceAccountScopes(scopes) .setServiceAccountPrivateKey((PrivateKey) keyStore.getKey("privateKey", p12Password.toCharArray()))//This is always the password for p12 files .build(); return creds; } public Compute getGoogleCompute() throws CloudException, InternalException { ProviderContext ctx = getContext(); Collection<GoogleCredential> cachedCredential = (Collection<GoogleCredential>)cachedCredentials.get(ctx); Collection<Compute> googleCompute = (Collection<Compute>)computeCache.get(ctx); try { final HttpTransport transport = getTransport(); if (cachedCredential == null || googleCompute == null) { cachedCredential = new ArrayList<GoogleCredential>(); cachedCredential.add(getCreds(transport, jsonFactory, ComputeScopes.all())); cachedCredentials.put(ctx, cachedCredential); } if (googleCompute == null) { googleCompute = new ArrayList<Compute>(); googleCompute.add((Compute) new Compute.Builder(transport, jsonFactory, cachedCredential.iterator().next()).setApplicationName(ctx.getAccountNumber()).setHttpRequestInitializer(initializer).build()); computeCache.put(ctx, googleCompute); } } catch(Exception ex) { throw new CloudException(CloudErrorType.AUTHENTICATION, 400, "Bad Credentials", "An authentication error has occurred: Bad Credentials"); } initializer.setStackedRequestInitializer(ctx, cachedCredential.iterator().next()); LogHandler.verifyInitialized(); return googleCompute.iterator().next(); } public Storage getGoogleStorage() throws CloudException, InternalException{ ProviderContext ctx = getContext(); Collection<GoogleCredential> cachedCredential = (Collection<GoogleCredential>)cachedCredentials.get(ctx); Collection<Storage> googleDrive = (Collection<Storage>)storageCache.get(ctx); try { final HttpTransport transport = getTransport(); if (cachedCredential == null || googleDrive == null) { cachedCredential = new ArrayList<GoogleCredential>(); cachedCredential.add(getCreds(transport, jsonFactory, ComputeScopes.all())); cachedCredentials.put(ctx, cachedCredential); } if (googleDrive == null) { googleDrive = new ArrayList<Storage>(); googleDrive.add((Storage) new Storage.Builder(transport, jsonFactory, cachedCredential.iterator().next()).setApplicationName(ctx.getAccountNumber()).setHttpRequestInitializer(initializer).build()); storageCache.put(ctx, googleDrive); } } catch(Exception ex) { throw new CloudException(CloudErrorType.AUTHENTICATION, 400, "Bad Credentials", "An authentication error has occurred: Bad Credentials"); } initializer.setStackedRequestInitializer(ctx, cachedCredential.iterator().next()); LogHandler.verifyInitialized(); return googleDrive.iterator().next(); } public SQLAdmin getGoogleSQLAdmin() throws CloudException, InternalException{ ProviderContext ctx = getContext(); Collection<GoogleCredential> cachedSqlCredential = (Collection<GoogleCredential>)cachedSqlCredentials.get(ctx); Collection<SQLAdmin> googleSql = (Collection<SQLAdmin>)sqlCache.get(ctx); try { final HttpTransport transport = getTransport(); if (cachedSqlCredential == null) { cachedSqlCredential = new ArrayList<GoogleCredential>(); cachedSqlCredential.add(getCreds(transport, jsonFactory, sqlScope)); cachedSqlCredentials.put(ctx, cachedSqlCredential); } if (googleSql == null) { googleSql = new ArrayList<SQLAdmin>(); googleSql.add((SQLAdmin) new SQLAdmin.Builder(transport, jsonFactory, cachedSqlCredential.iterator().next()).setApplicationName(ctx.getAccountNumber()).setHttpRequestInitializer(initializer).build()); sqlCache.put(ctx, googleSql); } } catch (Exception ex){ throw new CloudException(CloudErrorType.AUTHENTICATION, 400, "Bad Credentials", "An authentication error has occurred: Bad Credentials"); } initializer.setStackedRequestInitializer(ctx, cachedSqlCredential.iterator().next()); LogHandler.verifyInitialized(); return googleSql.iterator().next(); } @Override public @Nullable CIServices getCIServices() { return new GoogleCIServices(this); } public Replicapool getGoogleReplicapool() throws CloudException, InternalException{ ProviderContext ctx = getContext(); Collection<GoogleCredential> cachedCredential = (Collection<GoogleCredential>)cachedCredentials.get(ctx); Collection<Replicapool> replicaPool = (Collection<Replicapool>)replicapoolCache.get(ctx); try { final HttpTransport transport = getTransport(); if (cachedCredential == null) { cachedCredential = new ArrayList<GoogleCredential>(); cachedCredential.add(getCreds(transport, jsonFactory, sqlScope)); cachedCredentials.put(ctx, cachedCredential); } if (replicaPool == null) { replicaPool = new ArrayList<Replicapool>(); replicaPool.add((Replicapool) new Replicapool.Builder(transport, jsonFactory, cachedCredential.iterator().next()).setApplicationName(ctx.getAccountNumber()).setHttpRequestInitializer(initializer).build()); replicapoolCache.put(ctx, replicaPool); } } catch (Exception ex){ throw new CloudException(CloudErrorType.AUTHENTICATION, 400, "Bad Credentials", "An authentication error has occurred: Bad Credentials"); } initializer.setStackedRequestInitializer(ctx, cachedCredential.iterator().next()); LogHandler.verifyInitialized(); return replicaPool.iterator().next(); } @Override public @Nullable String testContext() { if (logger.isTraceEnabled()) logger.trace("ENTER - " + Google.class.getName() + ".testContext()"); NetHttpTransport httpTransport2 = new NetHttpTransport(); JacksonFactory jsonFactory2 = new JacksonFactory(); ProviderContext ctx = getContext(); if (ctx == null) return null; try { GoogleCredential creds = null; Compute googleCompute = null; try { creds = getCreds(httpTransport2, jsonFactory2, ComputeScopes.all()); googleCompute = new Compute.Builder(httpTransport2, jsonFactory2, creds).setApplicationName(ctx.getAccountNumber()).build(); googleCompute.networks().list(ctx.getAccountNumber()).execute(); return ctx.getAccountNumber(); } catch (Exception e) { logger.error("Error list firewalls failed: "); return null; } } finally { if (logger.isTraceEnabled()) logger.trace("EXIT - " + Google.class.getName() + ".textContext()"); } } public long parseTime(@Nullable String time) throws CloudException { if (time == null) { return 0L; } SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); if (time.length() > 0) { try { return fmt.parse(time).getTime(); } catch (ParseException e) { fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); try { return fmt.parse(time).getTime(); } catch (ParseException encore) { throw new CloudException("Could not parse date: " + time); } } } return 0L; } }